﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using DiscoveryLogic.Analytics.DataObjects;

namespace DiscoveryLogic.Analytics.Diagrams.Physics
{
	/// <summary>
	/// springs + particles
	/// </summary>
	public class ParticleSystem
	{
		#region props
		private Vector _Gravity;
		/// <summary></summary>
		public Vector Gravity
		{
			get
			{
				return this._Gravity;
			}
			set
			{
				this._Gravity = value;
			}
		}

		private double _DragFactor = 0.75;
		/// <summary></summary>
		public double DragFactor
		{
			get
			{
				return this._DragFactor;
			}
			set
			{
				this._DragFactor = Math.Min(1, Math.Max(0, value));
			}
		}

		private double _WallFriction = 0.1;
		/// <summary></summary>
		public double WallFriction
		{
			get
			{
				return this._WallFriction;
			}
			set
			{
				this._WallFriction = Math.Min(1, Math.Max(0, value));
			}
		}


		private List<Particle> _Particles;
		/// <summary></summary>
		public List<Particle> Particles
		{
			get
			{
				return this._Particles;
			}
            set
			{
				this._Particles = value;
			}
		}

		private List<Spring> _Springs;
		/// <summary></summary>
		public List<Spring> Springs
		{
			get
			{
				return this._Springs;
			}
			private set
			{
				this._Springs = value;
			}
		}
		#endregion

		#region ctor
		public ParticleSystem()
		{
			this._Particles=new List<Particle>();
			this._Springs=new List<Spring>();
			this._Gravity=new Vector(0.0, 20.0);
		}
		#endregion

		#region render
		public void Render(DrawingContext dc)
		{
			lock(this.Springs)
			{
				foreach(Spring spring in this.Springs)
				{
					spring.Render(dc);
				}
			}
		}
		#endregion

		#region calc
		public void CalculateDerivative()
		{
            foreach (Particle particle in Particles)
            {
                // Clear all existing forces acting on the particle
                particle.ResetForce();

                // Add a gravity force
                particle.AddForce(Gravity);

                // Add world drag
                Vector drag = particle.Velocity * - this._DragFactor;
                particle.AddForce(drag);
            }

            foreach (Spring spring in Springs)
            {
                // Apply what ever forces this spring holds
                spring.Apply();
            }

            foreach (Particle particle in Particles)
            {
                particle.State = new ParticleState(particle.Velocity, particle.Force * (particle.OneOverMass));
            }
		}

		public int DoEulerStep(double deltaTime, Rect constraintRectangle)
		{
			this.CalculateDerivative();
		    int particleMoved = 0;
			foreach(Particle particle in this.Particles)
			{
                particle.State.Position *= deltaTime;
                particle.State.Velocity *= deltaTime;

                particle.Velocity = particle.Velocity + particle.State.Velocity;

                Vector newPosition = particle.Position + particle.State.Position;

                // If the particle is supposed to be constrained to the canvas "visible" area
                // do collision detection and figure out new position and velocity
                if (particle.ConstrainedToConvas && !constraintRectangle.Contains(newPosition.ToPoint()))
                {
                    double x = particle.Velocity.X;
                    double y = particle.Velocity.Y;

                    // If particle is moving left and is to the left of the left canvas boundry
                    // clamp position and reverse velocity and damp velocity by wall friction
                    // This is repeated for each of the four borders.
                    if (particle.Velocity.X < 0 && newPosition.X < constraintRectangle.Left)
                    {
                        newPosition.X = constraintRectangle.Left;
                        x *= -(1.0 - this._WallFriction);
                    }

                    if (particle.Velocity.X > 0 && newPosition.X > constraintRectangle.Right)
                    {
                        newPosition.X = constraintRectangle.Right;
                        x *= -(1.0 - this._WallFriction);
                    }

                    if (particle.Velocity.Y < 0 && newPosition.Y < constraintRectangle.Top)
                    {
                        newPosition.Y = constraintRectangle.Top;
                        y *= -(1.0 - this._WallFriction);
                    }

                    if (particle.Velocity.Y > 0 && newPosition.Y > constraintRectangle.Bottom)
                    {
                        newPosition.Y = constraintRectangle.Bottom;
                        y *= -(1.0 - this._WallFriction);
                    }
                    particle.Velocity = new Vector(x, y);
                }


                if ((particle.Velocity - new Vector()).Length > 20 ||
                    (particle.Position - newPosition).Length > 20)
                    particleMoved++;

                particle.Position = newPosition;
			}

		    return particleMoved;
		}
		#endregion
	}
}
